﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace Patchworker
{
	public delegate void OnConfigChanged();

	public enum ETileMode{Combine, Split}

	public class ConfigContainer
	{

		#region Singleton
		private static ConfigContainer instance= null;

		private ConfigContainer()
		{
			init();
		}

		public static ConfigContainer Instance
		{
			get
			{
				if (instance == null)
				{
					instance = new ConfigContainer();
				}

				return instance;
			}
		}
		#endregion

		private void init()
		{
			images = new List<Image>();
		}

		#region Delegates
		//public OnConfigChanged refreshScreenHandler, refreshTilerHandler, refreshAnimationHandler;
		public OnConfigChanged refreshHandler;


		/// <summary>
		/// Refresh handlers from other application components. Acts only when Locked.
		/// </summary>
		private void refresh()
		{
			if (!locked)
			{
				if (refreshHandler != null)
				{
					refreshHandler();
				}
			}
		}

		/*
		private void refreshScreen()
		{
			if (refreshScreenHandler != null)
			{
				refreshScreenHandler();
			}
		}

		private void refreshTiler()
		{
			if (refreshTilerHandler != null)
			{
				refreshTilerHandler();
			}
		}

		private void refreshAnimation()
		{
			if (refreshAnimationHandler != null)
			{
				refreshAnimationHandler();
			}
		}
		 * 
		 * */
		#endregion

		private ETileMode currentMode = ETileMode.Combine;

		/// <summary>
		/// called when image is currently loaded.
		/// </summary>
		private bool imageLoaded = false;

		private Color backgroundColor;

		private Image backgroundImage;

		private bool drawBackgroundImage=false;

		private int numHorizontalGrids = 1;
		private int numVerticalGrids = 1;
		private Image image=null;
		private Bitmap bitmap=null;

		private List<Image> images;
		private bool isAtlasValid = false;

		private Image atlas;

		private Size tileSize;


		/// <summary>
		/// Sets and Gets current mode whether the application is now handling multiple images or not.
		/// </summary>
		public ETileMode Mode
		{
			get
			{
				return currentMode;
			}
			set
			{
				this.currentMode = value;
			}
		}

		/// <summary>
		/// Gets number of frames including empty tiles at the end.
		/// </summary>
		public int NumFullFrames
		{
			get
			{
				return numHorizontalGrids * numVerticalGrids;
			}
		}

		/// <summary>
		/// Gets actual number of tile images.
		/// </summary>
		public int NumImageFrames
		{
			get
			{
				switch (Mode)
				{
					default:
					case ETileMode.Combine:
						return images.Count;
					case ETileMode.Split:
						return NumFullFrames;
				}
			}
		}

		/// <summary>
		/// Gets whether the application is holding image or not.
		/// </summary>
		public bool ImageLoaded
		{
			get
			{
				return imageLoaded;
			}
		}

		/// <summary>
		/// Gets and Sets Background Color.
		/// </summary>
		public Color BackgroundColor
		{
			get
			{
				return backgroundColor;
			}
			set
			{
				if (backgroundColor != value)
				{
					this.backgroundColor = value;
					this.refresh();
				}
			}
		}

		/// <summary>
		/// Puts background Image in System. Not related to whether render it or not.
		/// </summary>
		public Image BackgroundImage
		{
			get
			{
				return backgroundImage;
			}
			set
			{
				if (backgroundImage != value)
				{
					if (backgroundImage != null) backgroundImage.Dispose();

					this.backgroundImage = value;
					refresh();
				}
			}
		}

		/// <summary>
		/// Sets and gets whether the application should render a background Image or not.
		/// </summary>
		public bool RenderBackgroundImage
		{
			get
			{
				return drawBackgroundImage;
			}
			set
			{
				if (drawBackgroundImage != value)
				{
					this.drawBackgroundImage = value;

					refresh();
				}
			}
		}

		/// <summary>
		///  calculate Tile Size with loaded image and designated number of tiles. it acts only in Split mode.
		/// </summary>
		public void initTileSize(int numHorizontalTiles, int numVerticalTiles)
		{
			if (Mode == ETileMode.Split)
			{
				this.numHorizontalGrids = numHorizontalTiles;
				this.numVerticalGrids = numVerticalTiles;

				if (image != null)
					this.tileSize = new Size(image.Width / numHorizontalTiles, image.Height / numVerticalTiles);

				refresh();
			}
		}

		/// <summary>
		/// Gets current tile size in pixels.
		/// </summary>
		public Size TileSize
		{
			get
			{
				return tileSize;
			}
		}

		public int NumHorizontalGrids
		{
			get
			{
				return numHorizontalGrids;
			}
			set
			{
				if (numHorizontalGrids != value)
				{
					numHorizontalGrids = value;

					if (Mode == ETileMode.Split)
					{
						initTileSize(numHorizontalGrids, numVerticalGrids);
					}
					else
					{
						calcNumVert();
					}

					isAtlasValid = false;

					refresh();
				}
			}
		}


		private void calcNumVert()
		{
			numVerticalGrids = (int)Math.Ceiling((double)images.Count / numHorizontalGrids);
		}

		public int NumVerticalGrids
		{
			get
			{
				return numVerticalGrids;
			}
			set
			{
				if (numVerticalGrids != value)
				{
					numVerticalGrids = value;

					initTileSize(numHorizontalGrids, numVerticalGrids);
					refresh();
				}
			}
		}

		/// <summary>
		/// Gets final Atlas of Current Settings.
		/// </summary>
		public Image Atlas
		{
			get
			{
				switch (Mode)
				{
					default:
					case ETileMode.Split:

						return image;

					case ETileMode.Combine:
						refreshAtlas();
						return atlas;
				}

			}
		}


		private void refreshAtlas()
		{
			//when current atlas is not valid, remake it
			if (images.Count != 0)
			{
				if (!isAtlasValid)
				{
					if (atlas != null)
					{
						atlas.Dispose();
					}
					atlas = new Bitmap(tileSize.Width * numHorizontalGrids, tileSize.Height * numVerticalGrids, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

					Graphics g = Graphics.FromImage(atlas);

					Rectangle srcRect = new Rectangle(0, 0, tileSize.Width, tileSize.Height);
					Rectangle destRect = new Rectangle(0, 0, tileSize.Width, tileSize.Height);

					for (int y = 0; y < numVerticalGrids; y++)
					{
						for (int x = 0; x < numHorizontalGrids; x++)
						{
							int imageIndex = y * numHorizontalGrids + x;

							destRect.X = x * tileSize.Width;
							destRect.Y = y * tileSize.Height;

							if (imageIndex < images.Count)
							{
								g.DrawImage(images[imageIndex], destRect, srcRect, GraphicsUnit.Pixel);
							}
						}
					}
					isAtlasValid = true;
				}
			}
		}

		/// <summary>
		/// put a Single Image Loaded in a Split Mode.
		/// </summary>
		public Image Image
		{
			set
			{
					if (image != null) image.Dispose();
					image = value;

					if (bitmap != null) bitmap.Dispose();
					bitmap = new Bitmap(image);

					this.imageLoaded = true;

					this.initTileSize(numHorizontalGrids, numVerticalGrids);

					refresh();
			}
		}

		#region lock
		private bool locked = false;

		/// <summary>
		/// Suspend auto refresh
		/// </summary>
		public void suspendRefresh()
		{
			locked = true;
		}

		/// <summary>
		/// Resume Auto refresh if it was suspended.
		/// </summary>
		/// <param name="immediateRefresh"></param>
		public void resumeRefresh(bool immediateRefresh)
		{
			locked = false;

			if (immediateRefresh)
			{
				refresh();
			}
		}

		/// <summary>
		/// Resume Auto refresh if it was suspended, and refresh immediately.
		/// </summary>
		public void resumeRefresh()
		{
			resumeRefresh(true);
		}



		#endregion

		public void RemoveTileAt(int index)
		{
			images.RemoveAt(index);
			calcNumVert();
			isAtlasValid = false;

			if (images.Count == 0)
				imageLoaded = false;

			refresh();
		}

		public void RemoveTile(Image image)
		{
			images.Remove(image);
			calcNumVert();
			isAtlasValid = false;

			if(images.Count==0)
			imageLoaded = false;

			refresh();
		}


		/// <summary>
		/// Sets Image Array of loaded tiles.
		/// </summary>
		public Image[] Images
		{
			set
			{
				images.Clear();

				images.AddRange(value);

				this.tileSize = images[0].Size;

				int numImages = images.Count;

				numHorizontalGrids = (int)Math.Ceiling(Math.Pow(numImages, 0.5));

				numVerticalGrids = (int)Math.Ceiling((double)numImages / numHorizontalGrids);

				isAtlasValid = false;

				imageLoaded = true;

				refresh();
			}
		}

		/// <summary>
		/// Swap Tiles
		/// </summary>
		/// <param name="aIndex"></param>
		/// <param name="bIndex"></param>
		public void swapImageTiles(int aIndex, int bIndex)
		{
			Image temp = images[aIndex];

			images[aIndex] = images[bIndex];
			images[bIndex] = temp;

			isAtlasValid = false;

			refresh();
		}

		public Bitmap Bitmap
		{
			get
			{
				return this.bitmap;
			}
		}

		

	}
}
